/* -------------------------------------------------------------------------
 *
 * contrib/sepgsql/relation.c
 *
 * 与关系/属性对象对应的例程
 *
 * Copyright (c) 2010-2022, PostgreSQL Global Development Group
 *
 * -------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/genam.h"
#include "access/htup_details.h"
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/dependency.h"
#include "catalog/pg_attribute.h"
#include "catalog/pg_class.h"
#include "catalog/pg_namespace.h"
#include "commands/seclabel.h"
#include "lib/stringinfo.h"
#include "sepgsql.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"

static void fc_sepgsql_index_modify(Oid fc_indexOid);

/*
 * sepgsql_attribute_post_create
 *
 * 此例程在使用 ALTER TABLE ... ADD COLUMN 定义的新列上分配默认安全标签。
 * 请注意，在 CREATE TABLE 的情况下不会调用此例程，
 * 尽管它也定义了除表之外的列。
 */
void sepgsql_attribute_post_create(Oid fc_relOid, AttrNumber fc_attnum)
{
	Relation	fc_rel;
	ScanKeyData fc_skey[2];
	SysScanDesc fc_sscan;
	HeapTuple	fc_tuple;
	char	   *fc_scontext;
	char	   *fc_tcontext;
	char	   *fc_ncontext;
	ObjectAddress fc_object;
	Form_pg_attribute fc_attForm;
	StringInfoData fc_audit_name;
	char		fc_relkind = get_rel_relkind(fc_relOid);

	/*
	 * 只有常规关系或分区关系中的属性具有单独的安全标签。
	 */
	if (fc_relkind != RELKIND_RELATION && fc_relkind != RELKIND_PARTITIONED_TABLE)
		return;

	/*
	 * 计算指定关系的新列的默认安全标签，并检查创建它的权限。
	 */
	fc_rel = table_open(AttributeRelationId, AccessShareLock);

	ScanKeyInit(&fc_skey[0],
				Anum_pg_attribute_attrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_relOid));
	ScanKeyInit(&fc_skey[1],
				Anum_pg_attribute_attnum,
				BTEqualStrategyNumber, F_INT2EQ,
				Int16GetDatum(fc_attnum));

	fc_sscan = systable_beginscan(fc_rel, AttributeRelidNumIndexId, true,
							   SnapshotSelf, 2, &fc_skey[0]);

	fc_tuple = systable_getnext(fc_sscan);
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "could not find tuple for column %d of relation %u",
			 fc_attnum, fc_relOid);

	fc_attForm = (Form_pg_attribute) GETSTRUCT(fc_tuple);

	fc_scontext = sepgsql_get_client_label();
	fc_tcontext = sepgsql_get_label(RelationRelationId, fc_relOid, 0);
	fc_ncontext = sepgsql_compute_create(fc_scontext, fc_tcontext,
									  SEPG_CLASS_DB_COLUMN,
									  NameStr(fc_attForm->attname));

	/*
	 * 检查 db_column:{create} 权限
	 */
	fc_object.classId = RelationRelationId;
	fc_object.objectId = fc_relOid;
	fc_object.objectSubId = 0;

	initStringInfo(&fc_audit_name);
	appendStringInfo(&fc_audit_name, "%s.%s",
					 getObjectIdentity(&fc_object, false),
					 quote_identifier(NameStr(fc_attForm->attname)));
	sepgsql_avc_check_perms_label(fc_ncontext,
								  SEPG_CLASS_DB_COLUMN,
								  SEPG_DB_COLUMN__CREATE,
								  fc_audit_name.data,
								  true);

	/*
	 * 为新过程分配默认安全标签
	 */
	fc_object.classId = RelationRelationId;
	fc_object.objectId = fc_relOid;
	fc_object.objectSubId = fc_attnum;
	SetSecurityLabel(&fc_object, SEPGSQL_LABEL_TAG, fc_ncontext);

	systable_endscan(fc_sscan);
	table_close(fc_rel, AccessShareLock);

	pfree(fc_tcontext);
	pfree(fc_ncontext);
}

/*
 * sepgsql_attribute_drop
 *
 * 检查删除所提供列的特权。
 */
void sepgsql_attribute_drop(Oid fc_relOid, AttrNumber fc_attnum)
{
	ObjectAddress fc_object;
	char	   *fc_audit_name;
	char		fc_relkind = get_rel_relkind(fc_relOid);

	if (fc_relkind != RELKIND_RELATION && fc_relkind != RELKIND_PARTITIONED_TABLE)
		return;

	/*
	 * 检查 db_column:{drop} 权限
	 */
	fc_object.classId = RelationRelationId;
	fc_object.objectId = fc_relOid;
	fc_object.objectSubId = fc_attnum;
	fc_audit_name = getObjectIdentity(&fc_object, false);

	sepgsql_avc_check_perms(&fc_object,
							SEPG_CLASS_DB_COLUMN,
							SEPG_DB_COLUMN__DROP,
							fc_audit_name,
							true);
	pfree(fc_audit_name);
}

/*
 * sepgsql_attribute_relabel
 *
 * 检查通过 `seclabel` 重新标记所提供列的特权。
 */
void sepgsql_attribute_relabel(Oid fc_relOid, AttrNumber fc_attnum,
						  const char *fc_seclabel)
{
	ObjectAddress fc_object;
	char	   *fc_audit_name;
	char		fc_relkind = get_rel_relkind(fc_relOid);

	if (fc_relkind != RELKIND_RELATION && fc_relkind != RELKIND_PARTITIONED_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot set security label on non-regular columns")));

	fc_object.classId = RelationRelationId;
	fc_object.objectId = fc_relOid;
	fc_object.objectSubId = fc_attnum;
	fc_audit_name = getObjectIdentity(&fc_object, false);

	/*
	 * 检查 db_column:{setattr relabelfrom} 权限
	 */
	sepgsql_avc_check_perms(&fc_object,
							SEPG_CLASS_DB_COLUMN,
							SEPG_DB_COLUMN__SETATTR |
							SEPG_DB_COLUMN__RELABELFROM,
							fc_audit_name,
							true);

	/*
	 * 检查 db_column:{relabelto} 权限
	 */
	sepgsql_avc_check_perms_label(fc_seclabel,
								  SEPG_CLASS_DB_COLUMN,
								  SEPG_DB_PROCEDURE__RELABELTO,
								  fc_audit_name,
								  true);
	pfree(fc_audit_name);
}

/*
 * sepgsql_attribute_setattr
 *
 * 检查更改所提供列的特权。
 */
void sepgsql_attribute_setattr(Oid fc_relOid, AttrNumber fc_attnum)
{
	ObjectAddress fc_object;
	char	   *fc_audit_name;
	char		fc_relkind = get_rel_relkind(fc_relOid);

	if (fc_relkind != RELKIND_RELATION && fc_relkind != RELKIND_PARTITIONED_TABLE)
		return;

	/*
	 * 检查 db_column:{setattr} 权限
	 */
	fc_object.classId = RelationRelationId;
	fc_object.objectId = fc_relOid;
	fc_object.objectSubId = fc_attnum;
	fc_audit_name = getObjectIdentity(&fc_object, false);

	sepgsql_avc_check_perms(&fc_object,
							SEPG_CLASS_DB_COLUMN,
							SEPG_DB_COLUMN__SETATTR,
							fc_audit_name,
							true);
	pfree(fc_audit_name);
}

/*
 * sepgsql_relation_post_create
 *
 * 关系/属性的创建后钩子
 */
void sepgsql_relation_post_create(Oid fc_relOid)
{
	Relation	fc_rel;
	ScanKeyData fc_skey;
	SysScanDesc fc_sscan;
	HeapTuple	fc_tuple;
	Form_pg_class fc_classForm;
	ObjectAddress fc_object;
	uint16_t	fc_tclass;
	char	   *fc_scontext;		/* subject */
	char	   *fc_tcontext;		/* schema */
	char	   *fc_rcontext;		/* 关系 */
	char	   *fc_ccontext;		/* column */
	char	   *fc_nsp_name;
	StringInfoData fc_audit_name;

	/*
	 * 获取新关系的目录记录。因为 pg_class 条目现在不可见，
	 * 我们需要使用 SnapshotSelf 扫描目录。
	 */
	fc_rel = table_open(RelationRelationId, AccessShareLock);

	ScanKeyInit(&fc_skey,
				Anum_pg_class_oid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_relOid));

	fc_sscan = systable_beginscan(fc_rel, ClassOidIndexId, true,
							   SnapshotSelf, 1, &fc_skey);

	fc_tuple = systable_getnext(fc_sscan);
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "could not find tuple for relation %u", fc_relOid);

	fc_classForm = (Form_pg_class) GETSTRUCT(fc_tuple);

	/* 忽略 toast 表上的索引 */
	if (fc_classForm->relkind == RELKIND_INDEX &&
		fc_classForm->relnamespace == PG_TOAST_NAMESPACE)
		goto out;

	/*
	 * 检查命名空间的 db_schema:{add_name} 权限
	 */
	fc_object.classId = NamespaceRelationId;
	fc_object.objectId = fc_classForm->relnamespace;
	fc_object.objectSubId = 0;
	sepgsql_avc_check_perms(&fc_object,
							SEPG_CLASS_DB_SCHEMA,
							SEPG_DB_SCHEMA__ADD_NAME,
							getObjectIdentity(&fc_object, false),
							true);

	switch (fc_classForm->relkind)
	{
		case RELKIND_RELATION:
		case RELKIND_PARTITIONED_TABLE:
			fc_tclass = SEPG_CLASS_DB_TABLE;
			break;
		case RELKIND_SEQUENCE:
			fc_tclass = SEPG_CLASS_DB_SEQUENCE;
			break;
		case RELKIND_VIEW:
			fc_tclass = SEPG_CLASS_DB_VIEW;
			break;
		case RELKIND_INDEX:
			/* 特别处理索引；无需 tclass */
			fc_sepgsql_index_modify(fc_relOid);
			goto out;
		default:
			/* 忽略其他 relkinds */
			goto out;
	}

	/*
	 * 在指定命名空间下创建新关系对象时计算默认安全标签。
	 */
	fc_scontext = sepgsql_get_client_label();
	fc_tcontext = sepgsql_get_label(NamespaceRelationId,
								 fc_classForm->relnamespace, 0);
	fc_rcontext = sepgsql_compute_create(fc_scontext, fc_tcontext, fc_tclass,
									  NameStr(fc_classForm->relname));

	/*
	 * 检查 db_xxx:{create} 权限
	 */
	fc_nsp_name = get_namespace_name(fc_classForm->relnamespace);
	initStringInfo(&fc_audit_name);
	appendStringInfo(&fc_audit_name, "%s.%s",
					 quote_identifier(fc_nsp_name),
					 quote_identifier(NameStr(fc_classForm->relname)));
	sepgsql_avc_check_perms_label(fc_rcontext,
								  fc_tclass,
								  SEPG_DB_DATABASE__CREATE,
								  fc_audit_name.data,
								  true);

	/*
	 * 在新的常规或分区关系上分配默认安全标签。
	 */
	fc_object.classId = RelationRelationId;
	fc_object.objectId = fc_relOid;
	fc_object.objectSubId = 0;
	SetSecurityLabel(&fc_object, SEPGSQL_LABEL_TAG, fc_rcontext);

	/*
	 * 我们还会在新表的列上分配默认安全标签。
	 */
	if (fc_classForm->relkind == RELKIND_RELATION ||
		fc_classForm->relkind == RELKIND_PARTITIONED_TABLE)
	{
		Relation	fc_arel;
		ScanKeyData fc_akey;
		SysScanDesc fc_ascan;
		HeapTuple	fc_atup;
		Form_pg_attribute fc_attForm;

		fc_arel = table_open(AttributeRelationId, AccessShareLock);

		ScanKeyInit(&fc_akey,
					Anum_pg_attribute_attrelid,
					BTEqualStrategyNumber, F_OIDEQ,
					ObjectIdGetDatum(fc_relOid));

		fc_ascan = systable_beginscan(fc_arel, AttributeRelidNumIndexId, true,
								   SnapshotSelf, 1, &fc_akey);

		while (HeapTupleIsValid(fc_atup = systable_getnext(fc_ascan)))
		{
			fc_attForm = (Form_pg_attribute) GETSTRUCT(fc_atup);

			resetStringInfo(&fc_audit_name);
			appendStringInfo(&fc_audit_name, "%s.%s.%s",
							 quote_identifier(fc_nsp_name),
							 quote_identifier(NameStr(fc_classForm->relname)),
							 quote_identifier(NameStr(fc_attForm->attname)));

			fc_ccontext = sepgsql_compute_create(fc_scontext,
											  fc_rcontext,
											  SEPG_CLASS_DB_COLUMN,
											  NameStr(fc_attForm->attname));

			/*
			 * 检查 db_column:{create} 权限
			 */
			sepgsql_avc_check_perms_label(fc_ccontext,
										  SEPG_CLASS_DB_COLUMN,
										  SEPG_DB_COLUMN__CREATE,
										  fc_audit_name.data,
										  true);

			fc_object.classId = RelationRelationId;
			fc_object.objectId = fc_relOid;
			fc_object.objectSubId = fc_attForm->attnum;
			SetSecurityLabel(&fc_object, SEPGSQL_LABEL_TAG, fc_ccontext);

			pfree(fc_ccontext);
		}
		systable_endscan(fc_ascan);
		table_close(fc_arel, AccessShareLock);
	}
	pfree(fc_rcontext);

out:
	systable_endscan(fc_sscan);
	table_close(fc_rel, AccessShareLock);
}

/*
 * sepgsql_relation_drop
 *
 * 检查删除所提供关系的特权。
 */
void sepgsql_relation_drop(Oid fc_relOid)
{
	ObjectAddress fc_object;
	char	   *fc_audit_name;
	uint16_t	fc_tclass = 0;
	char		fc_relkind = get_rel_relkind(fc_relOid);

	switch (fc_relkind)
	{
		case RELKIND_RELATION:
		case RELKIND_PARTITIONED_TABLE:
			fc_tclass = SEPG_CLASS_DB_TABLE;
			break;
		case RELKIND_SEQUENCE:
			fc_tclass = SEPG_CLASS_DB_SEQUENCE;
			break;
		case RELKIND_VIEW:
			fc_tclass = SEPG_CLASS_DB_VIEW;
			break;
		case RELKIND_INDEX:
			/* 忽略 toast 表上的索引 */
			if (get_rel_namespace(fc_relOid) == PG_TOAST_NAMESPACE)
				return;
			/* 其他索引在下面特别处理；无需 tclass */
			break;
		default:
			/* 忽略其他 relkinds */
			return;
	}

	/*
	 * 检查 db_schema:{remove_name} 权限
	 */
	fc_object.classId = NamespaceRelationId;
	fc_object.objectId = get_rel_namespace(fc_relOid);
	fc_object.objectSubId = 0;
	fc_audit_name = getObjectIdentity(&fc_object, false);

	sepgsql_avc_check_perms(&fc_object,
							SEPG_CLASS_DB_SCHEMA,
							SEPG_DB_SCHEMA__REMOVE_NAME,
							fc_audit_name,
							true);
	pfree(fc_audit_name);

	/* 特别处理索引 */
	if (fc_relkind == RELKIND_INDEX)
	{
		fc_sepgsql_index_modify(fc_relOid);
		return;
	}

	/*
	 * 检查 db_table/sequence/view:{drop} 权限
	 */
	fc_object.classId = RelationRelationId;
	fc_object.objectId = fc_relOid;
	fc_object.objectSubId = 0;
	fc_audit_name = getObjectIdentity(&fc_object, false);

	sepgsql_avc_check_perms(&fc_object,
							fc_tclass,
							SEPG_DB_TABLE__DROP,
							fc_audit_name,
							true);
	pfree(fc_audit_name);

	/*
	 * 检查 db_column:{drop} 权限
	 */
	if (fc_relkind == RELKIND_RELATION || fc_relkind == RELKIND_PARTITIONED_TABLE)
	{
		Form_pg_attribute fc_attForm;
		CatCList   *fc_attrList;
		HeapTuple	fc_atttup;
		int			fc_i;

		fc_attrList = SearchSysCacheList1(ATTNUM, ObjectIdGetDatum(fc_relOid));
		for (fc_i = 0; fc_i < fc_attrList->n_members; fc_i++)
		{
			fc_atttup = &fc_attrList->members[fc_i]->tuple;
			fc_attForm = (Form_pg_attribute) GETSTRUCT(fc_atttup);

			if (fc_attForm->attisdropped)
				continue;

			fc_object.classId = RelationRelationId;
			fc_object.objectId = fc_relOid;
			fc_object.objectSubId = fc_attForm->attnum;
			fc_audit_name = getObjectIdentity(&fc_object, false);

			sepgsql_avc_check_perms(&fc_object,
									SEPG_CLASS_DB_COLUMN,
									SEPG_DB_COLUMN__DROP,
									fc_audit_name,
									true);
			pfree(fc_audit_name);
		}
		ReleaseCatCacheList(fc_attrList);
	}
}

/*
 * sepgsql_relation_truncate
 *
 * 检查 TRUNCATE 所提供关系的特权。
 */
void sepgsql_relation_truncate(Oid fc_relOid)
{
	ObjectAddress fc_object;
	char	   *fc_audit_name;
	uint16_t	fc_tclass = 0;
	char		fc_relkind = get_rel_relkind(fc_relOid);

	switch (fc_relkind)
	{
		case RELKIND_RELATION:
		case RELKIND_PARTITIONED_TABLE:
			fc_tclass = SEPG_CLASS_DB_TABLE;
			break;
		default:
			/* 忽略其他 relkinds */
			return;
	}

	/*
	 * 检查 db_table:{truncate} 权限
	 */
	fc_object.classId = RelationRelationId;
	fc_object.objectId = fc_relOid;
	fc_object.objectSubId = 0;
	fc_audit_name = getObjectIdentity(&fc_object, false);

	sepgsql_avc_check_perms(&fc_object,
							fc_tclass,
							SEPG_DB_TABLE__TRUNCATE,
							fc_audit_name,
							true);
	pfree(fc_audit_name);
}

/*
 * sepgsql_relation_relabel
 *
 * 检查通过 `seclabel` 重新标记所提供关系的特权。
 */
void sepgsql_relation_relabel(Oid fc_relOid, const char *fc_seclabel)
{
	ObjectAddress fc_object;
	char	   *fc_audit_name;
	char		fc_relkind = get_rel_relkind(fc_relOid);
	uint16_t	fc_tclass = 0;

	if (fc_relkind == RELKIND_RELATION || fc_relkind == RELKIND_PARTITIONED_TABLE)
		fc_tclass = SEPG_CLASS_DB_TABLE;
	else if (fc_relkind == RELKIND_SEQUENCE)
		fc_tclass = SEPG_CLASS_DB_SEQUENCE;
	else if (fc_relkind == RELKIND_VIEW)
		fc_tclass = SEPG_CLASS_DB_VIEW;
	else
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot set security labels on relations except "
						"for tables, sequences or views")));

	fc_object.classId = RelationRelationId;
	fc_object.objectId = fc_relOid;
	fc_object.objectSubId = 0;
	fc_audit_name = getObjectIdentity(&fc_object, false);

	/*
	 * 检查 db_xxx:{setattr relabelfrom} 权限
	 */
	sepgsql_avc_check_perms(&fc_object,
							fc_tclass,
							SEPG_DB_TABLE__SETATTR |
							SEPG_DB_TABLE__RELABELFROM,
							fc_audit_name,
							true);

	/*
	 * 检查 db_xxx:{relabelto} 权限
	 */
	sepgsql_avc_check_perms_label(fc_seclabel,
								  fc_tclass,
								  SEPG_DB_TABLE__RELABELTO,
								  fc_audit_name,
								  true);
	pfree(fc_audit_name);
}

/*
 * sepgsql_relation_setattr
 *
 * 它检查设置所提供关系属性的权限
 */
void sepgsql_relation_setattr(Oid fc_relOid)
{
	Relation	fc_rel;
	ScanKeyData fc_skey;
	SysScanDesc fc_sscan;
	HeapTuple	fc_oldtup;
	HeapTuple	fc_newtup;
	Form_pg_class fc_oldform;
	Form_pg_class fc_newform;
	ObjectAddress fc_object;
	char	   *fc_audit_name;
	uint16_t	fc_tclass;

	switch (get_rel_relkind(fc_relOid))
	{
		case RELKIND_RELATION:
		case RELKIND_PARTITIONED_TABLE:
			fc_tclass = SEPG_CLASS_DB_TABLE;
			break;
		case RELKIND_SEQUENCE:
			fc_tclass = SEPG_CLASS_DB_SEQUENCE;
			break;
		case RELKIND_VIEW:
			fc_tclass = SEPG_CLASS_DB_VIEW;
			break;
		case RELKIND_INDEX:
			/* 特别处理索引 */
			fc_sepgsql_index_modify(fc_relOid);
			return;
		default:
			/* 其他 relkinds 不需要额外操作 */
			return;
	}

	/*
	 * 获取更新的目录
	 */
	fc_rel = table_open(RelationRelationId, AccessShareLock);

	ScanKeyInit(&fc_skey,
				Anum_pg_class_oid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_relOid));

	fc_sscan = systable_beginscan(fc_rel, ClassOidIndexId, true,
							   SnapshotSelf, 1, &fc_skey);

	fc_newtup = systable_getnext(fc_sscan);
	if (!HeapTupleIsValid(fc_newtup))
		elog(ERROR, "could not find tuple for relation %u", fc_relOid);
	fc_newform = (Form_pg_class) GETSTRUCT(fc_newtup);

	/*
	 * 获取旧的目录
	 */
	fc_oldtup = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_relOid));
	if (!HeapTupleIsValid(fc_oldtup))
		elog(ERROR, "cache lookup failed for relation %u", fc_relOid);
	fc_oldform = (Form_pg_class) GETSTRUCT(fc_oldtup);

	/*
	 * 此 ALTER 命令是否会对命名空间进行操作？
	 */
	if (fc_newform->relnamespace != fc_oldform->relnamespace)
	{
		sepgsql_schema_remove_name(fc_oldform->relnamespace);
		sepgsql_schema_add_name(fc_newform->relnamespace);
	}
	if (strcmp(NameStr(fc_newform->relname), NameStr(fc_oldform->relname)) != 0)
		sepgsql_schema_rename(fc_oldform->relnamespace);

	/*
	 * XXX - 在未来的版本中，如果表空间配置被更改，应检查系统目录项的 db_tuple:{use}。
	 */

	/*
	 * 检查 db_xxx:{setattr} 权限
	 */
	fc_object.classId = RelationRelationId;
	fc_object.objectId = fc_relOid;
	fc_object.objectSubId = 0;
	fc_audit_name = getObjectIdentity(&fc_object, false);

	sepgsql_avc_check_perms(&fc_object,
							fc_tclass,
							SEPG_DB_TABLE__SETATTR,
							fc_audit_name,
							true);
	pfree(fc_audit_name);

	ReleaseSysCache(fc_oldtup);
	systable_endscan(fc_sscan);
	table_close(fc_rel, AccessShareLock);
}

/*
 * sepgsql_relation_setattr_extra
 *
 * 它检查通过额外属性（如 pg_index 条目）引用的关系的权限。
 * 与核心 PostgreSQL 一样，sepgsql 也不将这些条目视为单独的“对象”，
 * 因此，这些条目的修改应视为设置底层关系的属性。
 */
static void fc_sepgsql_relation_setattr_extra(Relation fc_catalog,
							   Oid fc_catindex_id,
							   Oid fc_extra_oid,
							   AttrNumber fc_anum_relation_id,
							   AttrNumber fc_anum_extra_id)
{
	ScanKeyData fc_skey;
	SysScanDesc fc_sscan;
	HeapTuple	fc_tuple;
	Datum		fc_datum;
	bool		fc_isnull;

	ScanKeyInit(&fc_skey, fc_anum_extra_id,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_extra_oid));

	fc_sscan = systable_beginscan(fc_catalog, fc_catindex_id, true,
							   SnapshotSelf, 1, &fc_skey);
	fc_tuple = systable_getnext(fc_sscan);
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "could not find tuple for object %u in catalog \"%s\"",
			 fc_extra_oid, RelationGetRelationName(fc_catalog));

	fc_datum = heap_getattr(fc_tuple, fc_anum_relation_id,
						 RelationGetDescr(fc_catalog), &fc_isnull);
	Assert(!fc_isnull);

	sepgsql_relation_setattr(DatumGetObjectId(fc_datum));

	systable_endscan(fc_sscan);
}

/*
 * sepgsql_index_modify
 *		处理索引创建、更新、删除
 *
 * 与其他关系类型不同，索引没有自己的安全标签，
 * 因此不要直接进行检查，而是将它们视为所拥有表的额外属性；
 * 因此检查表上的 'setattr' 权限。
 */
static void fc_sepgsql_index_modify(Oid fc_indexOid)
{
	Relation	fc_catalog = table_open(IndexRelationId, AccessShareLock);

	/* 检查被索引表的 db_table:{setattr} 权限 */
	fc_sepgsql_relation_setattr_extra(fc_catalog,
								   IndexRelidIndexId,
								   fc_indexOid,
								   Anum_pg_index_indrelid,
								   Anum_pg_index_indexrelid);
	table_close(fc_catalog, AccessShareLock);
}
